package com.signway.swmediaplayer;

import static com.signway.swmediaplayer.SWMediaPlayer.*;

import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaPlayer;
import android.media.PlaybackParams;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceHolder;
import androidx.annotation.NonNull;
import com.signway.swmediaplayer.utils.SWLog;

import java.io.File;
import java.io.IOException;

// auto switch wait audio play finish don't interrupt.
// manual switch interrupt audio.
public class SWMediaManager {
    public static final String VIDEO = "video/";
    public static final String AUDIO = "audio/";
    private static final String TAG = SWMediaManager.class.getName();
    private SWLog swLog = new SWLog(SWMediaManager.class.getName(), true, true, false);
    protected static final long mCacheTimeMs = 460;/* 必须小于1S */
    protected static final long mCacheTimeNs = mCacheTimeMs * 1000_000;
    protected static final long mFrameIntervalUs = 40_000;//16666;/* 视频帧间隔 */

    private enum MediaCommStatus {
        MEDIA_COMM_STATUS_IDLE,
        MEDIA_COMM_STATUS_END,
        MEDIA_COMM_STATUS_ERROR,
        MEDIA_COMM_STATUS_INITIALIZED,
        MEDIA_COMM_STATUS_PREPARED,
        MEDIA_COMM_STATUS_PREPARING,
        MEDIA_COMM_STATUS_STARTED,
        MEDIA_COMM_STATUS_PAUSED,
        MEDIA_COMM_STATUS_STOPPED,
        MEDIA_COMM_STATUS_PLAYBACK_COMPLETED,
    };

    private volatile MediaCommStatus mMediaCommStatus;
    private String mNextFilePath = null;
    private boolean mLooping = false;
    private long mPauseTimeNs = 0;
    private SurfaceHolder mSurfaceHolder = null;
    private SWMediaPlayer mPlayer = null;
    private Handler mEventHandler = null;
    private SWAudioManager mSWAudioManager = null;
    private SWVideoManager mSWVideoManager = null;
    private boolean mAudioCodecCompletion = false;
    private boolean mVideoCodecCompletion = false;
    private PlaybackParams mPlaybackParams = new PlaybackParams();
    private MediaExtractor mExtractor = null;

    private static final Object mMutex = new Object();

    private long mAudioRTSUs = 0;
    private long mAudioPTSUs = 0;
    private long mVideoRTSUs = 0;
    private long mVideoPTSUs = 0;
    private long mCodecStartTimeUs = 0;

    private long mVideoTimeUs = 0;/* 当前视频Video时长 */
    private long mAudioTimeUs = 0;/* 当前视频Audio时长 */

    private OnMediaRenderListener mOnVideoRenderListener = new OnMediaRenderListener() {
        @Override
        public void renderTimestampNs(long renderTimestampNs, long presentationTimeUs) {
            swLog.logv("video setOnMediaRenderListener " + renderTimestampNs + ", presentationTimeUs " + presentationTimeUs);
            swLog.logv("video " + ((System.nanoTime() - renderTimestampNs) / 1000) + "(us)");

            mVideoRTSUs = renderTimestampNs / 1000;
            mVideoPTSUs = presentationTimeUs - mCodecStartTimeUs;

            long timestampMs = (renderTimestampNs - System.nanoTime()) / 1000_000;
            if (timestampMs < 0 || timestampMs > 800) {
                swLog.logi("video time " + (timestampMs) + "(ms)");
            }

            long deviationMs = Math.abs((mVideoRTSUs - mAudioRTSUs) - (mVideoPTSUs - mAudioPTSUs)) / 1000;
            if (deviationMs != 0) {
                swLog.logv("rts " + (mVideoRTSUs - mAudioRTSUs) + ", pts " + (mVideoPTSUs - mAudioPTSUs) +
                        ", " + deviationMs + "(ms)");
            }
        }

        @Override
        public void onCodecCompletion(String fileName, long startTimeUs) {
            Message mes = Message.obtain();
            mes.what = SW_MEDIA_VIDEO_CODEC_COMPLETION;
            Bundle bundle = new Bundle();
            bundle.putString("fileName", fileName);
            bundle.putLong("startTimeUs", startTimeUs);
            mes.setData(bundle);
            mEventHandler.sendMessage(mes);
        }

        @Override
        public void onError(String msg) {
            Message mes = Message.obtain();
            mes.what = SW_MEDIA_VIDEO_CODEC_ERROR;
            Bundle bundle = new Bundle();
            bundle.putString("msg", msg);
            mes.setData(bundle);
            mEventHandler.sendMessage(mes);
        }

        @Override
        public void onVideoSizeChanged(int width, int height) {
            if (width != videoWidth || height != videoHeight) {
                videoWidth = width;
                videoHeight = height;
                mEventHandler.sendEmptyMessage(SW_MEDIA_VIDEO_SIZE_CHANGED);
            }
        }

        @Override
        public void onInfo(int what, long extra) {
            swLog.logi("onInfo " + what + ", " + extra);
            switch (what) {
                case MEDIA_INFO_VIDEO_RENDERING_START: {
                    Message mes = Message.obtain();
                    mes.what = SW_MEDIA_ON_INFO;
                    mes.arg1 = MEDIA_INFO_VIDEO_RENDERING_START;
                    mes.arg2 = 0;
                    long delayMs = ((extra - System.nanoTime()) / 1_000_000) + 10;
                    Log.d(TAG, "MEDIA_INFO_VIDEO_RENDERING_START " + delayMs);
                    if (delayMs > 0) {
                        mEventHandler.sendMessageDelayed(mes, delayMs);
                    } else {
                        mEventHandler.sendMessage(mes);
                    }
                } break;
                case MEDIA_INFO_VIDEO_RENDERING_END: {
                    Message mes = Message.obtain();
                    mes.what = SW_MEDIA_ON_INFO;
                    mes.arg1 = MEDIA_INFO_VIDEO_RENDERING_END;
                    mes.arg2 = 0;
                    long delayMs = ((extra - System.nanoTime()) / 1_000_000) + 160;
                    Log.d(TAG, "MEDIA_INFO_VIDEO_RENDERING_END " + delayMs);
                    if (delayMs > 0) {
                        mEventHandler.sendMessageDelayed(mes, delayMs);
                    } else {
                        mEventHandler.sendMessage(mes);
                    }
                } break;
            }
        }
    };

    private OnMediaRenderListener mOnAudioRenderListener = new OnMediaRenderListener() {
        @Override
        public void renderTimestampNs(long renderTimestampNs, long presentationTimeUs) {
            swLog.logv("audio setOnMediaRenderListener " + renderTimestampNs + ", presentationTimeUs " + presentationTimeUs);
            mAudioRTSUs = renderTimestampNs / 1000;
            mAudioPTSUs = presentationTimeUs;

            long timestampMs = (System.nanoTime() - renderTimestampNs) / 1000_000;
            if (Math.abs(timestampMs) > 100) {
                swLog.logi("audio time " + (timestampMs) + "(ms)");
            }
        }

        @Override
        public void onCodecCompletion(String fileName, long startTimeUs) {
            Message mes = Message.obtain();
            mes.what = SW_MEDIA_AUDIO_CODEC_COMPLETION;
            Bundle bundle = new Bundle();
            bundle.putString("fileName", fileName);
            bundle.putLong("startTimeUs", startTimeUs);
            mes.setData(bundle);
            mEventHandler.sendMessage(mes);
        }

        @Override
        public void onError(String msg) {
            Message mes = Message.obtain();
            mes.what = SW_MEDIA_AUDIO_CODEC_ERROR;
            Bundle bundle = new Bundle();
            bundle.putString("msg", msg);
            mes.setData(bundle);
            mEventHandler.sendMessage(mes);
        }
    };

    public SWMediaManager(SWMediaPlayer player) {
        synchronized (mMutex) {
            mPlayer = player;
            Log.i(TAG, "[Performance]SWMediaManager() Enter HQF-2024-08-04");
            SWHandlerCallback callback = new SWHandlerCallback();
            Looper looper;
            if ((looper = Looper.myLooper()) != null) {
                this.mEventHandler = new Handler(looper, callback);
            } else if ((looper = Looper.getMainLooper()) != null) {
                this.mEventHandler = new Handler(looper, callback);
            } else {
                this.mEventHandler = null;
            }

            mSWVideoManager = new SWVideoManager(this);
            mSWAudioManager = new SWAudioManager(this);
            mSWVideoManager.setOnMediaRenderListener(mOnVideoRenderListener);
            mSWAudioManager.setOnMediaRenderListener(mOnAudioRenderListener);
            mMediaCommStatus = MediaCommStatus.MEDIA_COMM_STATUS_IDLE;
        }
    }

    public synchronized void switchDataSource() {
        if (mMediaCommStatus == MediaCommStatus.MEDIA_COMM_STATUS_PAUSED) {
            _start();
        }
        mSWAudioManager.switchDataSource();
        mSWVideoManager.switchDataSource();
    }

    public synchronized void setNextFilePath(String nextFilePath) throws IOException {
        swLog.logd("setNextFilePath " + nextFilePath);
        mNextFilePath = nextFilePath;
        if (mExtractor != null) {
            mExtractor.release();
        }
        mExtractor = new MediaExtractor();
        mExtractor.setDataSource(mNextFilePath);
        swLog.logd("setNextFilePath finish.");
    }

    public synchronized void setDataSource(String path) throws IOException {
        swLog.logd("setDataSource " + path);
        if (mMediaCommStatus != MediaCommStatus.MEDIA_COMM_STATUS_IDLE) {
            throw new IOException("the current state is not idle");
        }
        if (TextUtils.isEmpty(path)) {
            throw new IOException("data source is empty");
        }
        File file = new File(path);
        if (!(file.exists() && file.isFile() && file.canRead())) {
            throw new IOException("data source is error");
        }
        if (mEventHandler != null) {
            swLog.logi("setDataSource: remove all messages");
            mEventHandler.removeCallbacksAndMessages(null);
        }
        mNextFilePath = path;
        if (mExtractor != null) {
            mExtractor.release();
        }
        mExtractor = new MediaExtractor();
        mExtractor.setDataSource(mNextFilePath);
        mSWVideoManager.setDataSource(path);
        mSWAudioManager.setDataSource(path);
        mMediaCommStatus = MediaCommStatus.MEDIA_COMM_STATUS_INITIALIZED;
        swLog.logd("setDataSource finish.");
    }

    public synchronized void setCustomFormat(MediaFormat format) {
        mSWVideoManager.setCustomFormat(format);
    }

    public synchronized void setDisplay(SurfaceHolder sh) {
        swLog.logd("setDisplay " + sh);
        if (mMediaCommStatus != MediaCommStatus.MEDIA_COMM_STATUS_IDLE &&
            mMediaCommStatus != MediaCommStatus.MEDIA_COMM_STATUS_INITIALIZED) {
            swLog.loge("Not allowed to set.");
            return;
        }
        mSurfaceHolder = sh;
        mSWVideoManager.setDisplay(mSurfaceHolder);
        swLog.logd("setDataSource finish.");
    }

    public synchronized void prepare() throws IOException, IllegalStateException, InterruptedException {
        if (mMediaCommStatus != MediaCommStatus.MEDIA_COMM_STATUS_INITIALIZED) {
            swLog.loge("the current state is not initialized");
            return;
        }
        swLog.logd("prepare()");
        mMediaCommStatus = MediaCommStatus.MEDIA_COMM_STATUS_PREPARING;
        mSWVideoManager.prepare();
        mSWAudioManager.prepare();
        mMediaCommStatus = MediaCommStatus.MEDIA_COMM_STATUS_PREPARED;
        mEventHandler.sendEmptyMessage(SW_MEDIA_PREPARE_DONE);
        swLog.logd("prepare() finish.");
    }

    public synchronized void setLooping(boolean looping) {
        mLooping = looping;
    }

    public synchronized void stop() {
        swLog.logd("Do not support.");
    }

    public synchronized void start() {
        _start();
    }

    private void _start() {
        swLog.logd("start()");
        if (mMediaCommStatus == MediaCommStatus.MEDIA_COMM_STATUS_PREPARED) {
            mSWVideoManager.setStartWhenNs(System.nanoTime() + mCacheTimeNs);
            mSWAudioManager.setStartWhenNs(System.nanoTime() + mCacheTimeNs);
        } else if (mMediaCommStatus == MediaCommStatus.MEDIA_COMM_STATUS_PAUSED) {
            mPauseTimeNs = System.nanoTime() - mPauseTimeNs;
            swLog.logi("mPauseTimeNs " + mPauseTimeNs);
        } else if (mMediaCommStatus == MediaCommStatus.MEDIA_COMM_STATUS_PLAYBACK_COMPLETED) {
            swLog.logi("_start MEDIA_COMM_STATUS_PLAYBACK_COMPLETED");
        } else {
            swLog.loge("the current state is not prepared");
            return;
        }
        mSWVideoManager.start();
        mSWAudioManager.start();
        mMediaCommStatus = MediaCommStatus.MEDIA_COMM_STATUS_STARTED;
        swLog.logd("start() finish.");
    }

    public synchronized void pause() {
        if (mMediaCommStatus != MediaCommStatus.MEDIA_COMM_STATUS_STARTED) {
            return;
        }
        swLog.logd("pause()");
        mPauseTimeNs = System.nanoTime();
        mSWAudioManager.pause();
        mSWVideoManager.pause();
        mMediaCommStatus = MediaCommStatus.MEDIA_COMM_STATUS_PAUSED;
        swLog.logd("pause() finish.");
    }

    public synchronized void release() {
        swLog.logd("release()");
        synchronized (mMutex) {
            mEventHandler.removeCallbacksAndMessages(null);

            if (mExtractor != null) {
                mExtractor.release();
                mExtractor = null;
            }

            try {
                mSWVideoManager.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                mSWVideoManager = null;
            }

            try {
                mSWAudioManager.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                mSWAudioManager = null;
            }
        }
        swLog.logd("release() finish.");
    }

    public synchronized void reset() {
        swLog.logd("reset()");
        mSWVideoManager.reset();
        mSWAudioManager.reset();

        mVideoTimeUs = 0;
        mAudioTimeUs = 0;
        mCodecStartTimeUs = 0;

        mSWVideoManager.setStartWhenNs(0);
        mSWAudioManager.setStartWhenNs(0);

        mSWVideoManager.setCodecStartTimeUs(0);
        mSWAudioManager.setCodecStartTimeUs(0);

        if (mExtractor != null) {
            mExtractor.release();
            mExtractor = null;
        }

        mMediaCommStatus = MediaCommStatus.MEDIA_COMM_STATUS_IDLE;
        swLog.logd("reset() finish.");
    }

    public synchronized PlaybackParams getPlaybackParams() {
        return mPlaybackParams;
    }

    public synchronized void setPlaybackParams(PlaybackParams params) {
        mPlaybackParams = params;
    }

    public void setScreenOnWhilePlaying(boolean on) {
        if (this.mSurfaceHolder != null) {
            this.mSurfaceHolder.setKeepScreenOn(on);
        }
    }

    public int getVideoWidth() {
        return this.videoWidth;
    }

    public int getVideoHeight() {
        return this.videoHeight;
    }

    public synchronized int getDuration() {
        long durationUs = 0;
        if (mExtractor == null) {
            return -1;
        }

        for (int i = 0; i < mExtractor.getTrackCount(); i++) {
            MediaFormat mVideoFormat = mExtractor.getTrackFormat(i);
            String mime = mVideoFormat.getString(MediaFormat.KEY_MIME);
            if (mime.startsWith(VIDEO) || mime.startsWith(AUDIO)) {
                durationUs = Math.max(durationUs, mVideoFormat.getLong(MediaFormat.KEY_DURATION));
            }
        }

        return (int) (durationUs / 1_000);
    }

    private static final int SW_MEDIA_PREPARE_DONE = 6001;
    private static final int SW_MEDIA_ON_INFO = 6002;
    private static final int SW_MEDIA_END = 6003;
    private static final int SW_MEDIA_VIDEO_SIZE_CHANGED = 6004;
    private static final int SW_MEDIA_ON_CUES = 6005;
    private static final int SW_MEDIA_ON_SEEK_COMPLETE = 6006;
    private static final int SW_MEDIA_ON_ERROR = 6007;

    private static final int SW_MEDIA_ON_SWITCH = 7001;
    private static final int SW_MEDIA_VIDEO_CODEC_COMPLETION = 7002;
    private static final int SW_MEDIA_AUDIO_CODEC_COMPLETION = 7003;
    private static final int SW_MEDIA_VIDEO_CODEC_ERROR = 7004;
    private static final int SW_MEDIA_AUDIO_CODEC_ERROR = 7005;

    private int videoWidth = 0;
    private int videoHeight = 0;

    public class SWHandlerCallback implements android.os.Handler.Callback {
        @Override
        public boolean handleMessage(@NonNull Message message) {
            swLog.logi("handleMessage " + message.what);
            switch(message.what) {
                case SW_MEDIA_PREPARE_DONE: {
                    if (mPlayer.mOnPreparedListener != null) {
                        mPlayer.mOnPreparedListener.onPrepared(mPlayer);
                    }
                } break;
                case SW_MEDIA_ON_INFO: {
                    if (mPlayer.mOnInfoListener != null) {
                        swLog.logi("onInfo called: arg1=" + message.arg1);
                        mPlayer.mOnInfoListener.onInfo(mPlayer, message.arg1, message.arg2);
                    }
                } break;
                case SW_MEDIA_END: {
                    if (mPlayer.mOnCompletionListener != null) {
                        swLog.logi("onCompletion called mAudioTimeUs " + mAudioTimeUs + ", mVideoTimeUs" + mVideoTimeUs);

                        long presentationTimeUs = mCodecStartTimeUs + Math.max(mAudioTimeUs, mVideoTimeUs);
                        long lastRenderTimestampNs = presentationTimeUs * 1000 + mSWVideoManager.getStartWhenNs();

                        int ret = mPlayer.mOnCompletionListener.onCompletion(mPlayer, lastRenderTimestampNs);
                        switch (ret) {
                            case MEDIA_COMPLETION_CONTINUE:
                                if (!TextUtils.isEmpty(mNextFilePath)) {
                                    Message mes = Message.obtain();
                                    mes.what = SW_MEDIA_ON_SWITCH;
                                    Bundle bundle = new Bundle();
                                    bundle.putBoolean("start", true);
                                    bundle.putString("fileName", mNextFilePath);
                                    bundle.putString("startTimeUs", String.valueOf(presentationTimeUs));
                                    mes.setData(bundle);//mes利用Bundle传递数据
                                    mEventHandler.sendMessage(mes);
                                }
                                break;
                            case MEDIA_COMPLETION_STOP:
                                mSWVideoManager.pushEos();
                                swLog.logi("player finish.");
                                break;
                            case MEDIA_COMPLETION_PAUSE:
                                swLog.logi("player pause.");
                                mSWVideoManager.pushEos();
                                if (!TextUtils.isEmpty(mNextFilePath)) {
                                    Message mes = Message.obtain();
                                    mes.what = SW_MEDIA_ON_SWITCH;
                                    Bundle bundle = new Bundle();
                                    bundle.putBoolean("start", false);
                                    bundle.putString("fileName", mNextFilePath);
                                    bundle.putString("startTimeUs", String.valueOf(presentationTimeUs));
                                    mes.setData(bundle);//mes利用Bundle传递数据
                                    mEventHandler.sendMessage(mes);
                                }
                                break;
                        }
                    }
                } break;
                case SW_MEDIA_VIDEO_SIZE_CHANGED: {
                    if (mPlayer.mOnVideoSizeChangedListener != null) {
                        swLog.logi("onVideoSizeChanged called");
                        mPlayer.mOnVideoSizeChangedListener.onVideoSizeChanged(mPlayer, videoWidth, videoHeight);
                    }
                } break;
                case SW_MEDIA_ON_CUES: {
                    if (mPlayer.mOnInfoListener != null) {
                        swLog.logi("onInfo called: arg1=" + message.arg1);
                        mPlayer.mOnInfoListener.onInfo(mPlayer, -5015, 0);
                    }
                } break;
                case SW_MEDIA_ON_SEEK_COMPLETE: {
                    if (mPlayer.mOnSeekCompleteListener != null) {
                        mPlayer.mOnSeekCompleteListener.onSeekComplete(mPlayer);
                    }
                } break;
                case SW_MEDIA_ON_ERROR: {
                    if (mPlayer.mOnErrorListener != null) {
                        swLog.logi("onError called: arg1=" + message.arg1);
                        mPlayer.mOnErrorListener.onError(mPlayer, message.arg1, message.arg2);
                    }
                } break;
                case SW_MEDIA_ON_SWITCH: {
                    mEventHandler.removeMessages(SW_MEDIA_ON_SWITCH);
                    Bundle bundle = message.getData();
                    boolean start = bundle.getBoolean("start");
                    String fileName = bundle.getString("fileName");
                    long startTimeUs = Long.parseLong(bundle.getString("startTimeUs"));
                    synchronized (SWMediaManager.this) {
                        long startNs = System.nanoTime();
                        swLog.logi("SW_MEDIA_ON_SWITCH start. fileName: " + fileName + ", startTimeUs: " + startTimeUs);
                        try {
                            mCodecStartTimeUs = startTimeUs + mFrameIntervalUs;
                            mediaSwitch(fileName, mCodecStartTimeUs);
                            if (start) {
                                mSWVideoManager.start();
                                mSWAudioManager.start();
                            } else {
                                mSWVideoManager.pause();
                                mSWAudioManager.pause();
                                mMediaCommStatus = MediaCommStatus.MEDIA_COMM_STATUS_PLAYBACK_COMPLETED;
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        swLog.logi("SW_MEDIA_ON_SWITCH end " + ((System.nanoTime() - startNs) / 1_000_000));
                    }
                } break;
                case SW_MEDIA_AUDIO_CODEC_COMPLETION: {
                    swLog.logi("SW_MEDIA_AUDIO_CODEC_COMPLETION");
                    Bundle bundle = message.getData();
                    String fileName = bundle.getString("fileName");
                    long startTimeUs = bundle.getLong("startTimeUs");

                    mAudioTimeUs = startTimeUs - mCodecStartTimeUs;
                    swLog.logi("audio onCodecCompletion fileName: " + fileName + ", startTimeUs: " + startTimeUs + ", mAudioTimeUs: " + mAudioTimeUs);
                    mAudioCodecCompletion = true;
                    if (mVideoCodecCompletion) {
                        mVideoCodecCompletion = false;
                        mAudioCodecCompletion = false;
                        mEventHandler.sendEmptyMessage(SW_MEDIA_END);
                    }
                } break;
                case SW_MEDIA_VIDEO_CODEC_COMPLETION: {
                    swLog.logi("SW_MEDIA_VIDEO_CODEC_COMPLETION");
                    Bundle bundle = message.getData();
                    String fileName = bundle.getString("fileName");
                    long startTimeUs = bundle.getLong("startTimeUs");

                    mVideoTimeUs = startTimeUs - mCodecStartTimeUs;
                    swLog.logi("video onCodecCompletion fileName: " + fileName + ", startTimeUs: " + startTimeUs + ", mVideoTimeUs: " + mVideoTimeUs);
                    mVideoCodecCompletion = true;
                    if (mAudioCodecCompletion) {
                        mVideoCodecCompletion = false;
                        mAudioCodecCompletion = false;
                        mEventHandler.sendEmptyMessage(SW_MEDIA_END);
                    }
                } break;
                case SW_MEDIA_VIDEO_CODEC_ERROR: {
                    mVideoCodecCompletion = true;
                    swLog.logi("SW_MEDIA_VIDEO_CODEC_ERROR");
                } break;
                case SW_MEDIA_AUDIO_CODEC_ERROR: {
                    mAudioCodecCompletion = true;
                    swLog.logi("SW_MEDIA_AUDIO_CODEC_ERROR");
                } break;
                default:
                    return false;
            }
            return true;
        }
    }

    private void mediaSwitch(final String fileName, final long startTimeUs) throws InterruptedException {
        final long startNs = System.nanoTime();
        Thread videoThread = new Thread(() -> {
            swLog.logi("SW_MEDIA_ON_SWITCH video1 " + ((System.nanoTime() - startNs) / 1_000_000));
            try {
                mSWVideoManager.setNextDataSource(fileName);
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
            swLog.logi("SW_MEDIA_ON_SWITCH video2 " + ((System.nanoTime() - startNs) / 1_000_000));
        });
        videoThread.start();

        Thread audioThread = new Thread(() -> {
            try {
                swLog.logi("SW_MEDIA_ON_SWITCH audio1 " + ((System.nanoTime() - startNs) / 1_000_000));
                mSWAudioManager.resetWaitAndNoClear();
                mSWAudioManager.setDataSource(fileName);
                mSWAudioManager.prepare();
                swLog.logi("SW_MEDIA_ON_SWITCH audio2 " + ((System.nanoTime() - startNs) / 1_000_000));
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
        });
        audioThread.start();

        videoThread.join();
        audioThread.join();

        long initTimeUs = (System.nanoTime() - startNs) / 1000;
        swLog.logi("SW_MEDIA_ON_SWITCH initTimeUs " + initTimeUs);
        mSWVideoManager.setCodecStartTimeUs(startTimeUs + 40 * 1000 + initTimeUs);
        mSWAudioManager.setCodecStartTimeUs(startTimeUs + 40 * 1000 + initTimeUs);
    }

    public interface OnMediaRenderListener {
        /* 渲染回调 */
        default void renderTimestampNs(long renderTimestampNs, long presentationTimeUs){}
        /* 放入解码器完成回调 */
        default void onCodecCompletion(String fileName, long startTimeUs) {}
        default void onVideoSizeChanged(int width, int height) {}
        default void onError(String msg) {}

        default void onInfo(int what, long extra) {}
    }
}
